/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2018 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.scale;

import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.Map;
import org.eclipse.imagen.BorderExtender;
import org.eclipse.imagen.ImageLayout;
import org.eclipse.imagen.Interpolation;
import org.eclipse.imagen.InterpolationBicubic2;
import org.eclipse.imagen.InterpolationTable;
import org.eclipse.imagen.RasterAccessor;
import org.eclipse.imagen.RasterFormatTag;
import org.eclipse.imagen.iterator.RandomIter;
import org.eclipse.imagen.media.interpolators.InterpolationBicubic;
import org.eclipse.imagen.media.interpolators.InterpolationBilinear;
import org.eclipse.imagen.media.interpolators.InterpolationNearest;
import org.eclipse.imagen.media.interpolators.InterpolationNoData;
import org.eclipse.imagen.media.iterators.RandomIterFactory;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.media.util.ImageUtil;
import org.eclipse.imagen.media.util.Rational;
import org.huldra.math.BigInt;

/**
 * This class is an extends the functionality of the ScaleOpImage class by adding the support for No Data values and by
 * extending the ROI support for all the image types and for binary images. The interpolation type performed by the
 * scale operation is indicated by the Interpolation Object used. For Nearest-Neighbor, Bilinear, Bicubic/Bicubic2
 * interpolation type, the new Interpolation class InterpolationNearest, InterpolationBilinear, InterpolationBicubic
 * should be used for having optimized calculation on the scale operation, ROI and No Data support. If these special
 * Interpolation objects are not used, the interpolation is performed by using the interpolate() method of the
 * interpolator used in the selected kernel, but without ROI and No Data support. Another main difference from the old
 * Scale operations is the reduction of all the operations to one singular class instead of having various different
 * classes.
 */

// @SuppressWarnings("unchecked")
public class Scale2GeneralOpImage extends Scale2OpImage {

    /** Nearest-Neighbor interpolator */
    protected InterpolationNearest interpN = null;

    /** Bilinear interpolator */
    protected InterpolationBilinear interpB = null;

    /** Bicubic interpolator */
    protected InterpolationBicubic interpBN = null;

    // Simple constructor used for interpolators different from InterpolationNearest2, InterpolationBilinear2,
    // InterpolationBicubic
    public Scale2GeneralOpImage(
            RenderedImage source,
            ImageLayout layout,
            Map configuration,
            BorderExtender extender,
            Interpolation interp,
            double scaleX,
            double scaleY,
            double transX,
            double transY,
            boolean useRoiAccessor,
            Range nodata,
            double[] backgroundValues) {

        super(
                source,
                layout,
                configuration,
                true,
                extender,
                interp,
                scaleX,
                scaleY,
                transX,
                transY,
                useRoiAccessor,
                backgroundValues);
        scaleOpInitialization(source, interp, nodata, backgroundValues);
    }

    private void scaleOpInitialization(
            RenderedImage source, Interpolation interp, Range nodata, double[] backgroundValues) {
        // If the source has an IndexColorModel, override the default setting
        // in OpImage. The dest shall have exactly the same SampleModel and
        // ColorModel as the source.
        // Note, in this case, the source should have an integral data type.
        ColorModel srcColorModel = source.getColorModel();
        if (srcColorModel instanceof IndexColorModel && ImageUtil.isBinary(source.getSampleModel())) {
            sampleModel = source.getSampleModel().createCompatibleSampleModel(tileWidth, tileHeight);
            colorModel = srcColorModel;
        }

        // selection of the inverse scale parameters both for the x and y axis
        if (invScaleXRational.num > invScaleXRational.denom) {
            invScaleXInt = invScaleXRational.num / invScaleXRational.denom;
            invScaleXFrac = new BigInt(invScaleXRational.num % invScaleXRational.denom);
        } else {
            invScaleXInt = 0;
            invScaleXFrac = new BigInt(invScaleXRational.num);
        }

        if (invScaleYRational.num > invScaleYRational.denom) {
            invScaleYInt = invScaleYRational.num / invScaleYRational.denom;
            invScaleYFrac = new BigInt(invScaleYRational.num % invScaleYRational.denom);
        } else {
            invScaleYInt = 0;
            invScaleYFrac = new BigInt(invScaleYRational.num);
        }

        // Interpolator settings
        interpolator = interp;
        double[] destNod = null;
        if (backgroundValues != null && backgroundValues.length > 0) {
            destNod = backgroundValues;
        }
        if (interpolator instanceof InterpolationNearest) {
            isNearestNew = true;
            interpN = (InterpolationNearest) interpolator;
            this.interp = interpN;
            interpN.setROIBounds(roiBounds);
            if (destNod == null) {
                destNod = new double[] {interpN.getDestinationNoData()};
            }
        } else if (interpolator instanceof InterpolationBilinear) {
            isBilinearNew = true;
            interpB = (InterpolationBilinear) interpolator;
            this.interp = interpB;
            interpB.setROIBounds(roiBounds);
            if (destNod == null) {
                destNod = new double[] {interpB.getDestinationNoData()};
            }
        } else if (interpolator instanceof InterpolationBicubic) {
            isBicubicNew = true;
            interpBN = (InterpolationBicubic) interpolator;
            this.interp = interpBN;
            interpBN.setROIBounds(roiBounds);
            if (destNod == null) {
                destNod = new double[] {interpBN.getDestinationNoData()};
            }
        } else if (this.backgroundValues != null) {
            destNod = this.backgroundValues;
        }

        if (destNod == null) {
            destNod = new double[] {0d};
        }

        this.destinationNoDataDouble = destNod;
        if (interpolator instanceof InterpolationNoData) {
            InterpolationNoData interpolationNoData = (InterpolationNoData) interpolator;
            interpolationNoData.setDestinationNoData(destNod[0]);
            if (nodata != null) {
                hasNoData = true;
                interpolationNoData.setNoDataRange(nodata);
            }
            interpolationNoData.setUseROIAccessor(this.useRoiAccessor);
        }

        // subsample bits used for the bilinear and bicubic interpolation
        subsampleBits = interp.getSubsampleBitsH();

        // Internal precision required for position calculations
        one = 1 << subsampleBits;

        // Subsampling related variables
        shift2 = 2 * subsampleBits;
        round2 = 1 << (shift2 - 1);
        // Interpolation table used in the bicubic interpolation
        if (interpolator instanceof InterpolationTable) {
            InterpolationTable interpTable = (InterpolationTable) interpolator;
            precisionBits = interpTable.getPrecisionBits();
        }

        // Number of subsample positions
        one = 1 << subsampleBits;

        if (precisionBits > 0) {
            round = 1 << (precisionBits - 1);
        }

        // Get the width and height and padding of the Interpolation kernel.
        interp_width = interp.getWidth();
        interp_height = interp.getHeight();
        interp_left = interp.getLeftPadding();
        interp_top = interp.getTopPadding();

        SampleModel sm = source.getSampleModel();

        // Selection of the destination No Data
        switch (sm.getDataType()) {
            case DataBuffer.TYPE_BYTE:
                destinationNoDataByte = new byte[1];
                destinationNoDataByte[0] = (byte) (((byte) destinationNoDataDouble[0]) & 0xff);
                break;
            case DataBuffer.TYPE_USHORT:
                destinationNoDataUShort = new short[1];
                destinationNoDataUShort[0] = (short) (((short) destinationNoDataDouble[0]) & 0xffff);
                break;
            case DataBuffer.TYPE_SHORT:
                destinationNoDataShort = new short[1];
                destinationNoDataShort[0] = (short) destinationNoDataDouble[0];
                break;
            case DataBuffer.TYPE_INT:
                destinationNoDataInt = new int[1];
                destinationNoDataInt[0] = (int) destinationNoDataDouble[0];
                break;
            case DataBuffer.TYPE_FLOAT:
                destinationNoDataFloat = new float[1];
                destinationNoDataFloat[0] = (float) destinationNoDataDouble[0];
                break;
            case DataBuffer.TYPE_DOUBLE:
                break;
            default:
                throw new IllegalArgumentException("Wrong data Type");
        }

        // Special case -- if the image is represented using
        // a MultiPixelPackedSampleModel and a byte, ushort,
        // or int DataBuffer we can access the pixel data directly.
        // Note that there is a potential loophole that has not been
        // resolved by Java2D as to whether the underlying DataBuffer
        // must be of one of the standard types. Here we make the
        // assumption that it will be -- we can't check without
        // forcing an actual tile to be computed.
        //
        isBinary = (sm instanceof MultiPixelPackedSampleModel)
                && (sm.getSampleSize(0) == 1)
                && (sm.getDataType() == DataBuffer.TYPE_BYTE
                        || sm.getDataType() == DataBuffer.TYPE_USHORT
                        || sm.getDataType() == DataBuffer.TYPE_INT);
    }

    /** This method executes the scale operation on a selected region of the image */
    protected void computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect) {
        // Retrieve format tags.
        RasterFormatTag[] formatTags = getFormatTags();
        // Only one source raster is used
        Raster source = sources[0];

        // Get the source rectangle
        Rectangle srcRect = source.getBounds();

        // SRC and destination accessors are used for simplifying calculations
        RasterAccessor srcAccessor = new RasterAccessor(
                source, srcRect, formatTags[0], getSourceImage(0).getColorModel());

        RasterAccessor dstAccessor = new RasterAccessor(dest, destRect, formatTags[1], getColorModel());

        // Destination rectangle dimensions
        int dwidth = destRect.width;
        int dheight = destRect.height;
        // From the rasterAccessor are calculated the pixelStride and the scanLineStride
        int srcPixelStride = srcAccessor.getPixelStride();
        int srcScanlineStride = srcAccessor.getScanlineStride();
        // Initialization of the x and y position array
        int[] xpos = new int[dwidth];
        int[] ypos = new int[dheight];
        // Initialization of the x and y fractional position array
        Number xfracvalues[] = null, yfracvalues[] = null;

        // ROI support
        int[] yposRoi = null;
        // Scanline stride. It is used as integer because it can return null values
        Integer roiScanlineStride = null;
        // Roi rasterAccessor initialization
        RasterAccessor roiAccessor = null;
        // Roi raster initialization
        Raster roi = null;
        RandomIter roiIter = null;

        // ROI calculation only if the roi raster is present
        if (hasROI) {
            if (useRoiAccessor) {
                if (srcROIImage.getBounds().contains(srcRect)) {
                    roi = srcROIImage.getData(srcRect);
                } else {
                    roi = srcROIImgExt.getData(srcRect);
                }
                // creation of the rasterAccessor
                roiAccessor = new RasterAccessor(
                        roi,
                        srcRect,
                        RasterAccessor.findCompatibleTags(new RenderedImage[] {srcROIImage}, srcROIImage)[0],
                        srcROIImage.getColorModel());
                // ROI scanlinestride
                roiScanlineStride = roiAccessor.getScanlineStride();
                // Initialization of the roi y position array
                yposRoi = new int[dheight];
            } else {
                roiIter = RandomIterFactory.create(srcROIImgExt, roiRect, true, true);
            }
        }

        // destination data type
        dataType = dest.getSampleModel().getDataType();
        // initialization of the x and y fractional values
        yfracvalues = new Number[dheight];
        xfracvalues = new Number[dwidth];
        // Private method for calculating the x and y positions, x and y fractional positions and y roi positions if
        // present
        preComputePositions(
                destRect,
                srcRect.x,
                srcRect.y,
                srcPixelStride,
                srcScanlineStride,
                xpos,
                ypos,
                xfracvalues,
                yfracvalues,
                roiScanlineStride,
                yposRoi);
        // This methods differs only for the presence of the roi or if the image is a binary one
        if (isBinary) {
            computeLoopBynary(
                    srcAccessor,
                    source,
                    dest,
                    destRect,
                    xpos,
                    ypos,
                    yposRoi,
                    xfracvalues,
                    yfracvalues,
                    roi,
                    yposRoi,
                    srcRect.x,
                    srcRect.y,
                    roiIter);
        } else {
            if (hasROI) {
                computeLoop(
                        srcAccessor,
                        destRect,
                        dstAccessor,
                        xpos,
                        ypos,
                        xfracvalues,
                        yfracvalues,
                        roiAccessor,
                        roiIter,
                        yposRoi);
            } else {
                computeLoop(srcAccessor, destRect, dstAccessor, xpos, ypos, xfracvalues, yfracvalues, null, null, null);
            }

            if (dstAccessor.isDataCopy()) {
                if (dstAccessor.needsClamping()) {
                    dstAccessor.clampDataArrays();
                }
                dstAccessor.copyDataToRaster();
            }
        }
    }

    // This method precompute the integer and fractional position of every pixel
    private void preComputePositions(
            Rectangle destRect,
            int srcRectX,
            int srcRectY,
            int srcPixelStride,
            int srcScanlineStride,
            int xpos[],
            int ypos[],
            Number[] xfracvalues,
            Number[] yfracvalues,
            Integer roiScanlineStride,
            int[] yposRoi) {

        // Destination Rectangle position
        int dwidth = destRect.width;
        int dheight = destRect.height;

        // Loop variables based on the destination rectangle to be calculated.
        int dx = destRect.x;
        int dy = destRect.y;

        // Initially the y source value is calculated by the destination value and then performing the inverse
        // scale operation on it.
        // scale operation on it.
        BigInt syNum = new BigInt(dy);
        BigInt syDenom = new BigInt(1);

        // Subtract the X translation factor sy -= transY
        syNum = subnew(mulnew(syNum, transYRationalDenom), (mulnew(transYRationalNum, syDenom)));
        syDenom.mul(transYRationalDenom);

        // Add 0.5
        syNum.mul(2);
        syNum.add(syDenom);
        syDenom.mul(2);

        // Multply by invScaleX
        syNum.mul(invScaleYRationalNum);
        syDenom.mul(invScaleYRationalDenom);

        if (interpBN != null
                || interpB != null
                || interpolator instanceof InterpolationBilinear
                || interpolator instanceof InterpolationBicubic
                || interpolator instanceof InterpolationBicubic2) {
            /// Subtract 0.5
            syNum.mul(2);
            syNum.sub(syDenom);
            syDenom.mul(2);
        }

        // Separate the y source coordinate into integer and fractional part
        int srcYInt = Rational.floor(syNum.longValue(), syDenom.longValue());
        BigInt srcYFrac = modnew(syNum, syDenom);
        if (srcYInt < 0) {
            srcYFrac = addnew(syDenom, srcYFrac);
        }

        // Normalize - Get a common denominator for the fracs of src and invScaleY
        final BigInt commonYDenom = mulnew(syDenom, invScaleYRationalDenom);
        srcYFrac.mul(invScaleYRationalDenom);
        final BigInt newInvScaleYFrac = mulnew(invScaleYFrac, syDenom);

        // Initially the x source value is calculated by the destination value and then performing the inverse
        // scale operation on it.
        BigInt sxNum = new BigInt(dx);
        BigInt sxDenom = new BigInt(1);

        // Subtract the X translation factor sx -= transX
        sxNum.mul(transXRationalDenom);
        sxNum.sub(mulnew(transXRationalNum, sxDenom));
        sxDenom.mul(transXRationalDenom);

        // Add 0.5
        sxNum.mul(2);
        sxNum.add(sxDenom);
        sxDenom.mul(2);

        // Multply by invScaleX
        sxNum.mul(invScaleXRationalNum);
        sxDenom.mul(invScaleXRationalDenom);

        if (interpBN != null
                || interpB != null
                || interpolator instanceof InterpolationBilinear
                || interpolator instanceof InterpolationBicubic
                || interpolator instanceof InterpolationBicubic2) {
            // Subtract 0.5
            sxNum.mul(2);
            sxNum.sub(sxDenom);
            sxDenom.mul(2);
        }

        // Separate the x source coordinate into integer and fractional part
        int srcXInt = Rational.floor(sxNum.longValue(), sxDenom.longValue());
        BigInt srcXFrac = modnew(sxNum, sxDenom);
        if (srcXInt < 0) {
            srcXFrac = addnew(sxDenom, srcXFrac);
        }

        // Normalize - Get a common denominator for the fracs of src and invScaleX
        final BigInt commonXDenom = mulnew(sxDenom, invScaleXRationalDenom);
        srcXFrac.mul(invScaleXRationalDenom);
        final BigInt newInvScaleXFrac = mulnew(invScaleXFrac, sxDenom);

        // Store of the x positions
        for (int i = 0; i < dwidth; i++) {
            if (isBinary) {
                xpos[i] = srcXInt;
            } else {
                xpos[i] = (srcXInt - srcRectX) * srcPixelStride;
            }

            // Calculate the yfrac value
            if (dataType < DataBuffer.TYPE_FLOAT) {
                xfracvalues[i] = (int) (((1.0f * srcXFrac.floatValue()) / commonXDenom.floatValue()) * one);
            } else {
                xfracvalues[i] = (1.0f * srcXFrac.floatValue()) / commonXDenom.floatValue();
            }
            // Move onto the next source pixel.

            // Add the integral part of invScaleX to the integral part of srcX
            srcXInt += invScaleXInt;

            // Add the fractional part of invScaleX to the fractional part of srcX
            srcXFrac.add(newInvScaleXFrac);

            // If the fractional part is now greater than equal to the
            // denominator, divide so as to reduce the numerator to be less
            // than the denominator and add the overflow to the integral part.
            if (srcXFrac.compareTo(commonXDenom) >= 0) {
                srcXInt += 1;
                srcXFrac.sub(commonXDenom);
            }
        }
        // Store of the y positions
        for (int i = 0; i < dheight; i++) {

            // Calculate the source position in the source data array.
            if (isBinary) {
                ypos[i] = srcYInt;
            } else {
                ypos[i] = (srcYInt - srcRectY) * srcScanlineStride;
            }

            // If roi is present, the y position roi value is calculated
            if (yposRoi != null) {
                if (isBinary) {
                    yposRoi[i] = srcYInt;
                } else {
                    yposRoi[i] = (srcYInt - srcRectY) * roiScanlineStride;
                }
            }

            // Calculate the yfrac value
            if (dataType < DataBuffer.TYPE_FLOAT) {
                yfracvalues[i] = (int) (((1.0f * srcYFrac.floatValue()) / commonYDenom.floatValue()) * one);
            } else {
                yfracvalues[i] = (1.0f * srcYFrac.floatValue()) / commonYDenom.floatValue();
            }
            // Move onto the next source pixel.

            // Add the integral part of invScaleY to the integral part of srcY
            srcYInt += invScaleYInt;

            // Add the fractional part of invScaleY to the fractional part of srcY
            srcYFrac.add(newInvScaleYFrac);

            // If the fractional part is now greater than equal to the
            // denominator, divide so as to reduce the numerator to be less
            // than the denominator and add the overflow to the integral part.
            if (srcYFrac.compareTo(commonYDenom) >= 0) {
                srcYInt += 1;
                srcYFrac.sub(commonYDenom);
            }
        }
    }

    // Method for calculating the destination pixels without using the roiAccessor
    private void computeLoop(
            RasterAccessor src,
            Rectangle dstRect,
            RasterAccessor dst,
            int[] xpos,
            int[] ypos,
            Number[] xfracvalues,
            Number[] yfracvalues,
            RasterAccessor roi,
            RandomIter roiIter,
            int[] yposRoi) {

        // Source PixelStride and ScanLineStride and bandOffsets
        int srcPixelStride = src.getPixelStride();
        int srcScanlineStride = src.getScanlineStride();
        int bandOffsets[] = src.getBandOffsets();
        // Destination rectangle dimensions
        int dwidth = dstRect.width;
        int dheight = dstRect.height;
        // Destination image band numbers
        int dnumBands = dst.getNumBands();
        // Destination bandOffsets, PixelStride and ScanLineStride
        int dstBandOffsets[] = dst.getBandOffsets();
        int dstPixelStride = dst.getPixelStride();
        int dstScanlineStride = dst.getScanlineStride();

        // Destination and source data arrays (for all bands)
        // ONLY ONE OF THIS VALUES IS NOT NULL, THE OTHERS ARE ALL NULL
        byte[][] srcDataArraysByte = src.getByteDataArrays();
        short[][] srcDataArraysUshort = src.getShortDataArrays();
        short[][] srcDataArraysShort = src.getShortDataArrays();
        int[][] srcDataArraysInt = src.getIntDataArrays();
        float[][] srcDataArraysFloat = src.getFloatDataArrays();
        double[][] srcDataArraysDouble = src.getDoubleDataArrays();

        byte[][] dstDataArraysByte = dst.getByteDataArrays();
        short[][] dstDataArraysUshort = dst.getShortDataArrays();
        short[][] dstDataArraysShort = dst.getShortDataArrays();
        int[][] dstDataArraysInt = dst.getIntDataArrays();
        float[][] dstDataArraysFloat = dst.getFloatDataArrays();
        double[][] dstDataArraysDouble = dst.getDoubleDataArrays();

        // Destination and source data arrays (for a single band)
        byte[] dstDataByte = null;
        short[] dstDataUshort = null;
        short[] dstDataShort = null;
        int[] dstDataInt = null;
        float[] dstDataFloat = null;
        double[] dstDataDouble = null;

        byte[] srcDataByte = null;
        short[] srcDataUshort = null;
        short[] srcDataShort = null;
        int[] srcDataInt = null;
        float[] srcDataFloat = null;
        double[] srcDataDouble = null;

        // Array of samples required for the interpolation (Used only for general interpolators)
        int[][] samples = null;
        float[][] samplesf = null;
        double[][] samplesd = null;

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_INT:
                samples = new int[interp_height][interp_width];
                break;
            case DataBuffer.TYPE_FLOAT:
                samplesf = new float[interp_height][interp_width];
                break;
            case DataBuffer.TYPE_DOUBLE:
                samplesd = new double[interp_height][interp_width];
                break;
            default:
                break;
        }
        // Destination pixel returned
        Number s = null;
        // for all bands
        for (int k = 0; k < dnumBands; k++) {

            switch (dataType) {
                case DataBuffer.TYPE_BYTE:
                    srcDataByte = srcDataArraysByte[k];
                    dstDataByte = dstDataArraysByte[k];
                    break;
                case DataBuffer.TYPE_USHORT:
                    srcDataUshort = srcDataArraysUshort[k];
                    dstDataUshort = dstDataArraysUshort[k];
                    break;
                case DataBuffer.TYPE_SHORT:
                    srcDataShort = srcDataArraysShort[k];
                    dstDataShort = dstDataArraysShort[k];
                    break;
                case DataBuffer.TYPE_INT:
                    srcDataInt = srcDataArraysInt[k];
                    dstDataInt = dstDataArraysInt[k];
                    break;
                case DataBuffer.TYPE_FLOAT:
                    srcDataFloat = srcDataArraysFloat[k];
                    dstDataFloat = dstDataArraysFloat[k];
                    break;
                case DataBuffer.TYPE_DOUBLE:
                    srcDataDouble = srcDataArraysDouble[k];
                    dstDataDouble = dstDataArraysDouble[k];
                    break;
                default:
                    break;
            }
            // Line and band Offset initialization
            int dstlineOffset = dstBandOffsets[k];
            int bandOffset = bandOffsets[k];
            // cycle on the y values
            for (int j = 0; j < dheight; j++) {
                // pixel offset initialization
                int dstPixelOffset = dstlineOffset;
                // y position selection
                int posy = ypos[j] + bandOffset;
                // roi y position initialization
                Integer posyROI = null;
                // if roi accessor is used, roi position is calculated
                if (yposRoi != null && roi != null) {
                    posyROI = yposRoi[j];
                }
                // cycle on the x values
                for (int i = 0; i < dwidth; i++) {
                    // x position selection
                    int posx = xpos[i];
                    // fractional values selection
                    Number[] fracValues = {xfracvalues[i], yfracvalues[j]};

                    if (interpBN != null) {
                        // Bicubic/Bicubic2 interpolation(must be set at the interpolator creation)
                        s = interpBN.interpolate(
                                src, k, dnumBands, posx, posy, fracValues, posyROI, roi, roiIter, false);
                    } else if (interpB != null) {
                        // Bilinear interpolation
                        s = interpB.interpolate(
                                src, k, dnumBands, posx, posy, fracValues, posyROI, roi, roiIter, false);
                    } else if (interpN != null) {
                        // Nearest-Neighbor interpolation
                        s = interpN.interpolate(src, k, dnumBands, posx, posy, posyROI, roi, roiIter, false);
                    } else if (interpolator != null) {

                        // GENERAL CASE WITH INTERPOLATORS DIFFERENT FROM THE ABOVE ONES
                        int start = interp_left * srcPixelStride + interp_top * srcScanlineStride;
                        start = posx + posy - start;
                        int countH = 0, countV = 0;
                        // loop on all the kernel pixels for saving the pixel values
                        for (int yloop = 0; yloop < interp_height; yloop++) {

                            int startY = start;

                            for (int xloop = 0; xloop < interp_width; xloop++) {
                                switch (dataType) {
                                    case DataBuffer.TYPE_BYTE:
                                        samples[countV][countH++] = srcDataByte[start] & 0xff;
                                        break;
                                    case DataBuffer.TYPE_USHORT:
                                        samples[countV][countH++] = srcDataUshort[start] & 0xffff;
                                        break;
                                    case DataBuffer.TYPE_SHORT:
                                        samples[countV][countH++] = srcDataShort[start];
                                        break;
                                    case DataBuffer.TYPE_INT:
                                        samples[countV][countH++] = srcDataInt[start];
                                        break;
                                    case DataBuffer.TYPE_FLOAT:
                                        samplesf[countV][countH++] = srcDataFloat[start];
                                        break;
                                    case DataBuffer.TYPE_DOUBLE:
                                        samplesd[countV][countH++] = srcDataDouble[start];
                                        break;
                                    default:
                                        break;
                                }
                                start += srcPixelStride;
                            }
                            countV++;
                            countH = 0;
                            start = startY + srcScanlineStride;
                        }

                        // Perform the interpolation
                        switch (dataType) {
                            case DataBuffer.TYPE_BYTE:
                            case DataBuffer.TYPE_USHORT:
                            case DataBuffer.TYPE_SHORT:
                            case DataBuffer.TYPE_INT:
                                s = interp.interpolate(samples, fracValues[0].intValue(), fracValues[1].intValue());
                                break;
                            case DataBuffer.TYPE_FLOAT:
                                s = interp.interpolate(
                                        samplesf, fracValues[0].floatValue(), fracValues[1].floatValue());
                                break;
                            case DataBuffer.TYPE_DOUBLE:
                                s = interp.interpolate(
                                        samplesd, fracValues[0].floatValue(), fracValues[1].floatValue());
                                break;
                            default:
                                break;
                        }
                    } else {
                        throw new UnsupportedOperationException(
                                "Scale operation cannot be performed without an interpolator");
                    }
                    // The interpolated value is saved in the destination array
                    switch (dataType) {
                        case DataBuffer.TYPE_BYTE:
                            dstDataByte[dstPixelOffset] = (byte) (s.byteValue() & 0xff);
                            break;
                        case DataBuffer.TYPE_USHORT:
                            dstDataUshort[dstPixelOffset] = (short) (s.shortValue() & 0xffff);
                            break;
                        case DataBuffer.TYPE_SHORT:
                            dstDataShort[dstPixelOffset] = s.shortValue();
                            break;
                        case DataBuffer.TYPE_INT:
                            dstDataInt[dstPixelOffset] = s.intValue();
                            break;
                        case DataBuffer.TYPE_FLOAT:
                            dstDataFloat[dstPixelOffset] = s.floatValue();
                            break;
                        case DataBuffer.TYPE_DOUBLE:
                            dstDataDouble[dstPixelOffset] = s.doubleValue();
                            break;
                        default:
                            break;
                    }
                    // destination pixel offset update
                    dstPixelOffset += dstPixelStride;
                }
                // destination line offset update
                dstlineOffset += dstScanlineStride;
            }
        }
    }

    private void computeLoopBynary(
            RasterAccessor src,
            Raster source,
            WritableRaster dest,
            Rectangle destRect,
            int xvalues[],
            int yvalues[],
            int yvaluesROI[],
            Number[] xfracvalues,
            Number[] yfracvalues,
            Raster roi,
            int[] posYROI,
            int srcRectX,
            int srcRectY,
            RandomIter roiIter) {

        int dx = destRect.x;
        int dy = destRect.y;
        int dwidth = destRect.width;
        int dheight = destRect.height;

        MultiPixelPackedSampleModel sourceSM = (MultiPixelPackedSampleModel) source.getSampleModel();

        int sourceTransX = source.getSampleModelTranslateX();
        int sourceTransY = source.getSampleModelTranslateY();
        int sourceDataBitOffset = sourceSM.getDataBitOffset();
        int sourceScanlineStride = sourceSM.getScanlineStride();
        int sourcePixelStride = sourceSM.getPixelBitStride();

        MultiPixelPackedSampleModel roiSM = null;
        // int roiTransX =0;
        int roiTransY = 0;
        // int roiDataBitOffset =0;
        int roiScanlineStride = 0;
        DataBuffer roiDB = null;

        if (roi != null) {
            roiSM = (MultiPixelPackedSampleModel) roi.getSampleModel();

            // roiTransX = roi.getSampleModelTranslateX();
            roiTransY = roi.getSampleModelTranslateY();
            // roiDataBitOffset = roiSM.getDataBitOffset();
            roiScanlineStride = roiSM.getScanlineStride();

            roiDB = roi.getDataBuffer();
        }

        MultiPixelPackedSampleModel destSM = (MultiPixelPackedSampleModel) dest.getSampleModel();

        int destTransX = dest.getSampleModelTranslateX();
        int destTransY = dest.getSampleModelTranslateY();
        int destDataBitOffset = destSM.getDataBitOffset();
        int destScanlineStride = destSM.getScanlineStride();

        int[] sIntShortBytenum = new int[dwidth];
        int[] sshift = new int[dwidth];

        Number[] destData = null;
        Number[] sourceData = null;

        DataBuffer destDB = dest.getDataBuffer();
        DataBuffer sourceDB = source.getDataBuffer();

        byte[] sourceDataB = null;
        byte[] destDataB = null;

        short[] sourceDataS = null;
        short[] destDataS = null;

        int[] sourceDataI = null;
        int[] destDataI = null;

        int[] roiData = null;

        int bitshift = 0;
        int bitNum = 0;

        dataType = destSM.getDataType();

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
                DataBufferByte destDBByte = (DataBufferByte) destDB;
                DataBufferByte sourceDBByte = (DataBufferByte) sourceDB;

                sourceDataB = sourceDBByte.getData();
                destDataB = destDBByte.getData();

                destData = new Number[destDataB.length];
                for (int ii = 0; ii < destDataB.length; ii++) {
                    destData[ii] = destDataB[ii];
                }

                sourceData = new Number[sourceDataB.length];
                for (int ii = 0; ii < sourceDataB.length; ii++) {
                    sourceData[ii] = sourceDataB[ii];
                }

                bitshift = 3;
                bitNum = 7;

                break;
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_SHORT:
                DataBufferUShort destDBShort = (DataBufferUShort) destDB;
                DataBufferUShort sourceDBShort = (DataBufferUShort) sourceDB;

                sourceDataS = sourceDBShort.getData();
                destDataS = destDBShort.getData();

                destData = new Number[destDataS.length];
                for (int ii = 0; ii < destDataS.length; ii++) {
                    destData[ii] = destDataS[ii];
                }

                sourceData = new Number[sourceDataS.length];
                for (int ii = 0; ii < sourceDataS.length; ii++) {
                    sourceData[ii] = sourceDataS[ii];
                }

                bitshift = 4;
                bitNum = 15;

                break;
            case DataBuffer.TYPE_INT:
                DataBufferInt destDBInt = (DataBufferInt) destDB;
                DataBufferInt sourceDBInt = (DataBufferInt) sourceDB;

                sourceDataI = sourceDBInt.getData();
                destDataI = destDBInt.getData();

                destData = new Number[destDataI.length];
                for (int ii = 0; ii < destDataI.length; ii++) {
                    destData[ii] = destDataI[ii];
                }

                sourceData = new Number[sourceDataI.length];
                for (int ii = 0; ii < sourceDataI.length; ii++) {
                    sourceData[ii] = sourceDataI[ii];
                }

                bitshift = 5;
                bitNum = 31;

                break;
        }

        if (roi != null) {
            DataBufferByte roiDBByte = (DataBufferByte) roiDB;
            byte[] roiDataB = roiDBByte.getData();
            roiData = new int[roiDataB.length];
            for (int ii = 0; ii < roiDataB.length; ii++) {
                roiData[ii] = roiDataB[ii];
            }
        }

        int sourceDBOffset = sourceDB.getOffset();
        int roiDBOffset = 0;
        int destDBOffset = destDB.getOffset();

        if (roi != null) {
            roiDBOffset = roiDB.getOffset();
        }

        for (int i = 0; i < dwidth; i++) {
            int x = xvalues[i];
            int sbitnum = sourceDataBitOffset + (x - sourceTransX);
            sIntShortBytenum[i] = sbitnum >> bitshift;
            sshift[i] = bitNum - (sbitnum & bitNum);
        }

        int sourceYOffset;

        int s;

        int x, y;
        int yfrac, xfrac;

        int xNextBitNo;

        int destYOffset = (dy - destTransY) * destScanlineStride + destDBOffset;

        int dbitnum = destDataBitOffset + (dx - destTransX);

        int destByteShortIntNum;
        int destBitShift;

        int[] coordinates = new int[2];

        for (int j = 0; j < dheight; j++) {

            y = yvalues[j];
            yfrac = yfracvalues[j].intValue();
            sourceYOffset = (y - sourceTransY) * sourceScanlineStride + sourceDBOffset;
            dbitnum = destDataBitOffset + (dx - destTransX);

            int yROI = 0;
            int roiYOffset = 0;

            if (roi != null) {
                yROI = posYROI[j];
                roiYOffset = (yROI - roiTransY) * roiScanlineStride + roiDBOffset;
            }
            for (int i = 0; i < dwidth; i++) {
                xfrac = xfracvalues[i].intValue();

                x = xvalues[i];
                coordinates[0] = src.getX() + (x - srcRectX) * sourcePixelStride;
                coordinates[1] = src.getY() + ((y - srcRectY) * sourceScanlineStride) / sourceScanlineStride;

                xNextBitNo = sourceDataBitOffset + (x + 1 - sourceTransX);

                if (interpN != null) {
                    s = interpN.interpolateBinary(
                            xNextBitNo,
                            sourceData,
                            sourceYOffset,
                            sourceScanlineStride,
                            coordinates,
                            roiData,
                            roiYOffset,
                            roiScanlineStride,
                            roiIter);
                } else if (interpB != null) {

                    s = interpB.interpolateBinary(
                            xNextBitNo,
                            sourceData,
                            xfrac,
                            yfrac,
                            sourceYOffset,
                            sourceScanlineStride,
                            coordinates,
                            roiData,
                            roiYOffset,
                            roiScanlineStride,
                            roiIter);

                } else if (interpBN != null) {

                    s = interpBN.interpolateBinary(
                            xNextBitNo,
                            sourceData,
                            xfrac,
                            yfrac,
                            sourceYOffset,
                            sourceScanlineStride,
                            coordinates,
                            roiData,
                            roiYOffset,
                            roiScanlineStride,
                            roiIter);
                } else {
                    throw new UnsupportedOperationException(
                            "Binary interpolation not supported by interpolator different from"
                                    + "the ones that belong to InterpolationNearest2, InterpolationBilinear2 or InterpolationBicubic"
                                    + "class.");
                }

                destByteShortIntNum = dbitnum >> bitshift;
                destBitShift = bitNum - (dbitnum & bitNum);

                if (s == 1) {

                    switch (dataType) {
                        case DataBuffer.TYPE_BYTE:
                            destDataB[destYOffset + destByteShortIntNum] |= (0x01 << destBitShift);
                            break;
                        case DataBuffer.TYPE_USHORT:
                        case DataBuffer.TYPE_SHORT:
                            destDataS[destYOffset + destByteShortIntNum] |= (0x01 << destBitShift);
                            break;
                        case DataBuffer.TYPE_INT:
                            destDataI[destYOffset + destByteShortIntNum] |= (0x01 << destBitShift);
                            break;
                    }
                } else {

                    switch (dataType) {
                        case DataBuffer.TYPE_BYTE:
                            destDataB[destYOffset + destByteShortIntNum] &= (0xff - (0x01 << destBitShift));
                            break;
                        case DataBuffer.TYPE_USHORT:
                        case DataBuffer.TYPE_SHORT:
                            destDataS[destYOffset + destByteShortIntNum] &= (0xffff - (0x01 << destBitShift));
                            break;
                        case DataBuffer.TYPE_INT:
                            destDataI[destYOffset + destByteShortIntNum] &= (0xffffffff - (0x01 << destBitShift));
                            break;
                    }
                }
                dbitnum++;
            }
            destYOffset += destScanlineStride;
        }
    }
}
